package hk.edu.cityu.is.riskmgmt.algorithm.pricing;

import hk.edu.cityu.is.riskmgmt.algorithm.random.MT;

import java.io.FileReader;
import java.util.Random;

import org.apache.commons.math.linear.CholeskyDecompositionImpl;
import org.apache.commons.math.linear.NonSquareMatrixException;
import org.apache.commons.math.linear.NotPositiveDefiniteMatrixException;
import org.apache.commons.math.linear.NotSymmetricMatrixException;
import org.apache.commons.math.linear.RealMatrix;
import org.apache.commons.math.linear.RealMatrixImpl;
import org.apache.commons.math.random.RandomDataImpl;
import org.apache.commons.math.stat.StatUtils;
import org.apache.commons.math.stat.correlation.Covariance;
import org.apache.commons.math.stat.correlation.PearsonsCorrelation;
import org.apache.commons.math.stat.descriptive.moment.StandardDeviation;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.supercsv.cellprocessor.Optional;
import org.supercsv.cellprocessor.ParseDouble;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.io.CsvBeanReader;
import org.supercsv.io.ICsvBeanReader;
import org.supercsv.prefs.CsvPreference;

/**
 * @author chaolu
 * 
 */
/**
 * PLAnalysis
 */
public class PLAnalysis implements java.io.Serializable {
	/**
	 * Logger for this class
	 */
	private static final Logger logger = Logger.getLogger(PLAnalysis.class);

	/**
	 * @param args
	 */
	private int stocknum;
	private int days = 60;
	private int alldays = 61;
	private double[][] prices;
	private double[][] stockReturn;
	private double[] mean;
	private double[] stdev;
	private double[][] correlationData;
	private RealMatrix covarianceMatrix;
	private RealMatrix cholsekyMatrix;
	private RealMatrix returnMatrix;
	private int start = 0;
	private int end = 60;

	private double[] lastPrices;
	static final CellProcessor[] userProcessors = new CellProcessor[] {
			new Optional(new ParseDouble()), new Optional(new ParseDouble()),
			new Optional(new ParseDouble()) };

	public PLAnalysis(double[][] stock_price, int start, int end, int stocknum,
			int days, int alldays) {
		this.prices = stock_price;
		this.days = days;
		this.start = start;
		this.stocknum = stocknum;
		// this.prices = new double[stocknum][alldays];
		this.stockReturn = new double[stocknum][alldays];
		this.mean = new double[stocknum];
		this.stdev = new double[stocknum];
		this.correlationData = new double[stocknum][stocknum];
		this.lastPrices = new double[stocknum];
		// prices = new RealMatrixImpl(prices).transpose().getData();
	}

	/**
	 * This function is used to generate a random historical prices for next
	 * step.
	 */
	private void generateRandomPrices() {
		Random randomGenerator;
		for (int idx1 = 0; idx1 < this.stocknum; idx1++) {
			for (int idx2 = 0; idx2 < alldays; idx2++) {
				long seed = System.currentTimeMillis();
				// System.out.println(seed);
				MT m = new MT();
				double randomDouble = m.random();
				this.prices[idx1][idx2] = randomDouble;
				// System.out.println("Price: " + this.prices[idx1][idx2]);

			}
		}
	}

	private void readFromCSV() {

		try {
			ICsvBeanReader inFile = new CsvBeanReader(new FileReader(
					"d:\\prices.csv"), CsvPreference.EXCEL_PREFERENCE);
			final String[] header = inFile.getCSVHeader(true);
			StockPrice sp;
			int idx = 0;
			while ((sp = inFile.read(StockPrice.class, header, userProcessors)) != null) {
				if (idx >= alldays) {
					break;
				}
				prices[0][idx] = sp.getPrice1();
				prices[1][idx] = sp.getPrice2();
				prices[2][idx] = sp.getPrice3();
				idx++;
			}
		} catch (Exception e) {
			logger.error("readFromCSV()", e); //$NON-NLS-1$
		}

	}

	private void getReturns() {
		for (int idx1 = 0; idx1 < this.stocknum; idx1++) {
			for (int idx2 = 0; idx2 < days; idx2++) {
				this.stockReturn[idx1][idx2] = Math.log(this.prices[idx1][idx2]
						/ this.prices[idx1][idx2 + 1]);
				// System.out.println("Return: " +idx1+": "+idx2+" "
				// +this.stockReturn[idx1][idx2]);

			}
		}
	}

	private void getInterestRateReturns() {
		for (int idx1 = 0; idx1 < this.stocknum; idx1++) {
			for (int idx2 = 0; idx2 < days; idx2++) {
				this.stockReturn[idx1][idx2] = Math
						.log(1 + this.prices[idx1][idx2] / 100);
				// System.out.println("Return: " +idx1+": "+idx2+" "
				// +this.stockReturn[idx1][idx2]);

			}
		}
	}

	/**
	 * Get mean prices of all stocks.
	 */
	private void getMean() {
		for (int idx = 0; idx < stocknum; idx++) {
			this.mean[idx] = StatUtils.mean(this.stockReturn[idx]);
			// System.out.println("Mean: " + this.mean[idx]);
		}
	}

	private void getStdev() {
		StandardDeviation sd = new StandardDeviation();
		for (int idx = 0; idx < stocknum; idx++) {
			this.stdev[idx] = sd.evaluate(this.stockReturn[idx]);
			System.out.println("Stdev: " + this.stdev[idx]);
		}
	}

	private void getCorrelationMatrix() {
		RealMatrix correlationMatrix;
		PearsonsCorrelation pc = new PearsonsCorrelation();
		correlationMatrix = pc.computeCorrelationMatrix(this.stockReturn);
		correlationData = correlationMatrix.getData();

	}

	private void getCovarianceMatrix() {
		RealMatrix rm = new RealMatrixImpl(this.stockReturn).transpose();
		Covariance co = new Covariance(rm);
		covarianceMatrix = co.getCovarianceMatrix();

	}

	private void getLastPrices() {
		for (int idx = 0; idx < stocknum; idx++) {
			// System.out.println("idx: "+idx+", days-1: "+(days-1));
			this.lastPrices[idx] = this.prices[idx][days - 1];
			// System.out.println(this.prices);
			// this.lastPrices[idx] = this.prices[days-1][idx];

		}
	}

	private double[] getNextPrices() {

		RandomDataImpl randomData = new RandomDataImpl();
		double[] nextPrices = new double[stocknum];
		for (int idx = 0; idx < stocknum; idx++) {
			MT m = new MT();
			randomData.reSeed(m.random());
			if (this.stdev[idx] == 0) {
				nextPrices[idx] = this.lastPrices[idx];
			} else {
				double stock_return = randomData.nextGaussian(this.mean[idx],
						this.stdev[idx]);

				nextPrices[idx] = this.lastPrices[idx] * Math.exp(stock_return);
			}// System.out.println(nextPrices[idx]);
		}
		return nextPrices;
	}

	private void choleskyTransform() {

		try {
			// System.out.println(covarianceMatrix);
			RealMatrix rm = null;
			CholeskyDecompositionImpl cdi = new CholeskyDecompositionImpl(
					covarianceMatrix, 0, -1.0E-5);
			// System.out.println(covarianceMatrix);
			rm = cdi.getL();
			cholsekyMatrix = rm;
		} catch (NonSquareMatrixException e) {
			// TODO Auto-generated catch block
			logger.error("choleskyTransform()", e); //$NON-NLS-1$
		} catch (NotSymmetricMatrixException e) {
			// TODO Auto-generated catch block
			logger.error("choleskyTransform()", e); //$NON-NLS-1$
		} catch (NotPositiveDefiniteMatrixException e) {
			// TODO Auto-generated catch block
			logger.error("choleskyTransform()", e); //$NON-NLS-1$
		}

	}

	private void getLastReturn() {
		RealMatrix rm = new RealMatrixImpl(stockReturn).transpose();
		this.returnMatrix = cholsekyMatrix.preMultiply(rm);
	}

	private void simulateWithoutInput(int times) {

		double[][] allResults = new double[times][stocknum];
		// generateRandomPrices();
		// readFromCSV();
		if (logger.isInfoEnabled()) {
			logger.info("simulateWithoutInput(int) - stocknum: " + stocknum); //$NON-NLS-1$
		}
		getReturns();
		getCovarianceMatrix();
		choleskyTransform();
		if (cholsekyMatrix != null) {
			getLastReturn();
		}
		getMean();
		getStdev();
		// getCorrelationMatrix();

		getLastPrices();
		for (int idx = 0; idx < times; idx++) {
			allResults[idx] = getNextPrices();
		}
		double data[][] = new RealMatrixImpl(allResults).transpose().getData();
		if (logger.isInfoEnabled()) {
			logger.info("simulateWithoutInput(int) - The average prices are: " + StatUtils.mean(data[0]) + ", " + StatUtils.mean(data[1]) + ", " + StatUtils.mean(data[2])); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}

	}

	public double[] simulateWithInput(int times) {

		double[][] allResults = new double[times][stocknum];
		// generateRandomPrices();
		// readFromCSV();
		if (logger.isInfoEnabled()) {
			logger.info("simulateWithInput(int) - stocknum: " + stocknum); //$NON-NLS-1$
		}
		getReturns();
		getCovarianceMatrix();
		choleskyTransform();
		if (cholsekyMatrix != null) {
			getLastReturn();
		}
		getMean();
		getStdev();
		// getCorrelationMatrix();

		getLastPrices();
		for (int idx = 0; idx < times; idx++) {
			allResults[idx] = getNextPrices();
		}
		double data[][] = new RealMatrixImpl(allResults).transpose().getData();
		double[] result = new double[stocknum];
		for (int i = 0; i < stocknum; i++) {
			result[i] = StatUtils.mean(data[i]);
		}
		return result;

	}

	public double[] simulateWithCSV(int times) {

		double[][] allResults = new double[times][stocknum];
		// generateRandomPrices();
		readFromCSV();
		if (logger.isInfoEnabled()) {
			logger.info("simulateWithInput(int) - stocknum: " + stocknum); //$NON-NLS-1$
		}
		getReturns();
		getCovarianceMatrix();
		choleskyTransform();
		if (cholsekyMatrix != null) {
			getLastReturn();
		}
		getMean();
		getStdev();
		// getCorrelationMatrix();

		getLastPrices();
		for (int idx = 0; idx < times; idx++) {
			allResults[idx] = getNextPrices();
		}
		double data[][] = new RealMatrixImpl(allResults).transpose().getData();
		double[] result = new double[stocknum];
		for (int i = 0; i < stocknum; i++) {
			result[i] = StatUtils.mean(data[i]);
		}
		return result;

	}

	public double[] simulateWithDist(int times, double meantimes,
			double vartimes) {

		double[][] allResults = new double[times][stocknum];
		// generateRandomPrices();
		// readFromCSV();
		if (logger.isInfoEnabled()) {
			logger.info("simulateWithInput(int) - stocknum: " + stocknum); //$NON-NLS-1$
		}
		getReturns();
		getCovarianceMatrix();
		choleskyTransform();
		if (cholsekyMatrix != null) {
			getLastReturn();
		}
		getMean();
		getStdev();
		for (int i = 0; i < this.stocknum; i++) {
			this.mean[i] = this.mean[i] * meantimes;
			this.stdev[i] = this.stdev[i] * vartimes;
		}
		// getCorrelationMatrix();

		getLastPrices();
		for (int idx = 0; idx < times; idx++) {
			allResults[idx] = getNextPrices();
		}
		double data[][] = new RealMatrixImpl(allResults).transpose().getData();
		double[] result = new double[stocknum];
		for (int i = 0; i < stocknum; i++) {
			result[i] = StatUtils.mean(data[i]);
		}
		return result;

	}

	public static void main(String[] args) {

	}

}
